Groovy简介与使用

您所在的位置:网站首页 groovy call Groovy简介与使用

Groovy简介与使用

2024-07-17 19:11| 来源: 网络整理| 查看: 265

简介 使用 应用 Groovy语法特性(相比于Java) Groovy与Java项目集成使用 GroovyShell GroovyClassLoader GroovyScriptEngine JSR-223 Groovy实现相关原理 Groovy代码文件与class文件的对应关系 对于没有任何类定义 对于仅有一个类 对于多个类 对于有定义类的脚本 Spring对Groovy以及动态语言的支持 Groovy运行沙盒 简介

Groovy是构建在JVM上的一个轻量级却强大的动态语言, 它结合了Python、Ruby和Smalltalk的许多强大的特性.

Groovy就是用Java写的 , Groovy语法与Java语法类似, Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码, 相对于Java, 它在编写代码的灵活性上有非常明显的提升,Groovy 可以使用其他 Java 语言编写的库.

使用

下载SDK

Groovy Console 安装IDEA groovy插件 应用

ElasticSearch, Jenkins 都支持执行Groovy脚本 项目构建工具Gradle就是Groovy实现的

Groovy语法特性(相比于Java)

不需要分号

return关键字可省略, 方法的最后一句表达式可作为返回值返回 (视具体情况使用, 避免降低可读性)

类的默认作用域是public, 不需要getter/setter方法

def关键字定义的变量类型都是Object, 任何变量, 方法都能用def定义/声明 , 在 Groovy 中 “一切都是对象 "

导航操作符 ( ?. )可帮助实现对象引用不为空时方法才会被调用

// java if (object != null) { object.getFieldA(); } // groovy object?.getFieldA()

命令链, Groovy 可以使你省略顶级语句方法调用中参数外面的括号。“命令链”功能则将这种特性继续扩展,它可以将不需要括号的方法调用串接成链,既不需要参数周围的括号,链接的调用之间也不需要点号

def methodA(String name) { println("A: " + name) return this } def methodB(String name) { println("B: " + name) return this } def methodC() { println("C") return this } def methodD(String name) { println("D: " + name) return this } methodA("xiaoming") methodB("zhangsan") methodC() methodD("lisi") // 不带参数的链中需要用括号 methodA "xiaoming" methodB "zhangsan" methodC() methodD "lisi"

闭包. 闭包是一个短的匿名代码块。每个闭包会被编译成继承groovy.lang.Closure类的类,这个类有一个叫call方法,通过该方法可以传递参数并调用这个闭包.

def hello = {println "Hello World"} hello.call() // 包含形式参数 def hi = { person1, person2 -> println "hi " + person1 + ", "+ person2 } hi.call("xiaoming", "xiaoli") // 隐式单个参数, 'it'是Groovy中的关键字 def hh = { println("haha, " + it) } hh.call("zhangsan")

with语法, (闭包实现)

// Java public class JavaDeamo { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.DECEMBER); calendar.set(Calendar.DATE, 4); calendar.set(Calendar.YEAR, 2018); Date time = calendar.getTime(); System.out.println(time); } } // Groovy Calendar calendar = Calendar.getInstance() calendar.with { // it 指 calendar 这个引用 it.set(Calendar.MONTH, Calendar.DECEMBER) // 可以省略it, 使用命令链 set Calendar.DATE, 4 set Calendar.YEAR, 2018 // calendar.getTime() println(getTime()) // 省略get, 对于get开头的方法名并且 println(time) }

数据结构的原生语法, 写法更便捷

def list = [11, 12, 13, 14] // 列表, 默认是ArrayList def list = ['Angular', 'Groovy', 'Java'] as List // 字符串列表 // 同list.add(8) list [of: { n -> action(what(n)) }] }] } // DSL 语言: please show the square_root of 100 (请显示100的平方根) // 调用, 等同于:please(show).the(square_root).of(100) please show the square_root of 100 // ==> 10.0 Java 的 == 实际相当于 Groovy 的 is() 方法,而 Groovy 的 == 则是一个更巧妙的 equals()。 在Groovy中要想比较对象的引用,不能用 ==,而应该用 a.is(b) http://www.groovy-lang.org/syntax.html Differences with Java: http://www.groovy-lang.org/differences.html

Groovy与Java项目集成使用

项目中引入groovy依赖

org.codehaus.groovy groovy-all x.y.z

常见的集成机制:

GroovyShell

GroovyClassLoader

GroovyScriptEngine

JSR 223 javax.script API

http://www.groovy-lang.org/integrating.html GroovyShell

GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果

解析为脚本(groovy.lang.Script)运行

GroovyShell groovyShell = new GroovyShell(); groovyShell.evaluate("println \"hello world\""); GroovyClassLoader

用 Groovy 的 GroovyClassLoader ,动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。

GroovyClassLoader loader = new GroovyClassLoader(); Class groovyClass = loader.parseClass(new File(groovyFileName)); // 也可以解析字符串 GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance(); groovyObject.invokeMethod("run", "helloworld"); GroovyScriptEngine

groovy.util.GroovyScriptEngine 类为 GroovyClassLoader 其上再增添一个能够处理脚本依赖及重新加载的功能层, GroovyScriptEngine可以从指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本

你可以使用一个CLASSPATH集合(url或者路径名称)初始化GroovyScriptEngine,之后便可以让它根据要求去执行这些路径中的Groovy脚本了.GroovyScriptEngine同样可以跟踪相互依赖的脚本,如果其中一个被依赖的脚本发生变更,则整个脚本树都会被重新编译和加载。

GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(file.getAbsolutePath()); groovyScriptEngine.run("hello.groovy", new Binding()) JSR-223

JSR-223 是 Java 中标准的脚本框架调用 API。从 Java 6 开始引入进来,主要目用来提供一种常用框架,以便从 Java 中调用多种语言

ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy"); // 编译成类 groovyEngine.compile(script) // 直接执行 groovyEngine.eval(script)

Groovy实现相关原理

groovy负责词法、语法解析groovy文件,然后用ASM生成普通的java字节码文件,供jvm使用。

Groovy代码文件与class文件的对应关系

作为基于JVM的语言,Groovy可以非常容易的和Java进行互操作,但也需要编译成class文件后才能运行,所以了解Groovy代码文件和class文件的对应关系,有助于更好地理解Groovy的运行方式和结构。

对于没有任何类定义

如果Groovy脚本文件里只有执行代码,没有定义任何类(class),则编译器会生成一个Script的子类,类名和脚本文件的文件名一样,而脚本的代码会被包含在一个名为run的方法中,同时还会生成一个main方法,作为整个脚本的入口。

对于仅有一个类

如果Groovy脚本文件里仅含有一个类,而这个类的名字又和脚本文件的名字一致,这种情况下就和Java是一样的,即生成与所定义的类一致的class文件, Groovy类都会实现groovy.lang.GroovyObject接口。

对于多个类

如果Groovy脚本文件含有一个或多个类,groovy编译器会很乐意地为每个类生成一个对应的class文件。如果想直接执行这个脚本,则脚本里的第一个类必须有一个static的main方法。

对于有定义类的脚本

如果Groovy脚本文件有执行代码, 并且有定义类, 那么所定义的类会生成对应的class文件, 同时, 脚本本身也会被编译成一个Script的子类,类名和脚本文件的文件名一样

Spring对Groovy以及动态语言的支持

Spring 从2.0开始支持将动态语言集成到基于 Spring 的应用程序中。Spring 开箱即用地支持 Groovy、JRuby 和 BeanShell。以 Groovy、JRuby 或任何受支持的语言编写的应用程序部分可以无缝地集成到 Spring 应用程序中。应用程序其他部分的代码不需要知道或关心单个 Spring bean 的实现语言。

动态语言支持将 Spring 从一个以 Java 为中心的应用程序框架改变成一个以 JVM 为中心的应用程序框架

Spring 通过 ScriptFactory 和 ScriptSource 接口支持动态语言集成。ScriptFactory 接口定义用于创建和配置脚本 Spring bean 的机制。理论上,所有在 JVM 上运行语言都受支持,因此可以选择特定的语言来创建自己的实现。ScriptSource 定义 Spring 如何访问实际的脚本源代码;例如,通过文件系统, URL, 数据库。

在使用基于 Groovy 的 bean 时,则有几种选择:

将 Groovy 类编译成普通的 Java 类文件

在一个 .groovy 文件中定义 Groovy 类或脚本

在 Spring 配置文件中以内联方式编写 Groovy 脚本

配置编译的 Groovy 类, 和Java一样的用法, 定义groovy class, 使用创建bean class Test { def printDate() { println(new Date()); } } ClassPathXmlApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml"); Test bean = (Test) context.getBean("test"); bean.printDate();

配置来自 Groovy 脚本的 bean

示例: 示例:

实现过程:

Groovy 语言集成通过 ScriptFactory 的 GroovyScriptFactory 实现得到支持

当 Spring 装载应用程序上下文时,它首先创建工厂 bean(这里是GroovyScriptFactory 类型的bean)。然后,执行 ScriptFactoryPostProcessor bean中的postProcessBeforeInstantiation方法,用实际的脚本对象替换所有的工厂 bean。

ScriptFactoryPostProcessor:

public Object postProcessBeforeInstantiation(Class beanClass, String beanName) { // 只处理ScriptFactory类型的bean if (!ScriptFactory.class.isAssignableFrom(beanClass)) { return null; } // ... // 加载并解析groovy代码, 在scriptBeanFactory中注册BeanDefinition prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); // ... } // prepareScriptBeans调用createScriptedObjectBeanDefinition protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName, ScriptSource scriptSource, @Nullable Class[] interfaces) { GenericBeanDefinition objectBd = new GenericBeanDefinition(bd); objectBd.setFactoryBeanName(scriptFactoryBeanName); // 指定工厂方法, ScriptFactory.getScriptedObject, 创建脚本的Java对象 objectBd.setFactoryMethodName("getScriptedObject"); objectBd.getConstructorArgumentValues().clear(); objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource); objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces); return objectBd; }

创建bean的时候, SimpleInstantiationStrategy.instantiate

// 调用工厂方法创建beanInstance Object result = factoryMethod.invoke(factoryBean, args); if (result == null) { result = new NullBean(); }

GroovyScriptFactory.getScriptedObject

// 通过groovyClassLoader 加载并解析类 this.scriptClass = getGroovyClassLoader().parseClass( scriptSource.getScriptAsString(), scriptSource.suggestedClassName()); if (Script.class.isAssignableFrom(this.scriptClass)) { // 如果是groovy 脚本, 那么运行脚本, 将结果的类作为Bean的类型 Object result = executeScript(scriptSource, this.scriptClass); this.scriptResultClass = (result != null ? result.getClass() : null); return result; } else { // 不是脚本, 直接返回类 this.scriptResultClass = this.scriptClass; } protected Object executeScript(ScriptSource scriptSource, Class scriptClass) throws ScriptCompilationException { try { GroovyObject goo = (GroovyObject) ReflectionUtils.accessibleConstructor(scriptClass).newInstance(); // GroovyObjectCustomizer 是一个回调,Spring 在创建一个 Groovy bean 之后会调用它。可以对一个 Groovy bean 应用附加的逻辑,或者执行元编程 if (this.groovyObjectCustomizer != null) { this.groovyObjectCustomizer.customize(goo); } if (goo instanceof Script) { // A Groovy script, probably creating an instance: let's execute it. return ((Script) goo).run(); } else { // An instance of the scripted class: let's return it as-is. return goo; } } catch (NoSuchMethodException ex) { // ... }

最终在ScriptFactoryPostProcessor中, scriptBeanFactory保存了所有通过脚本创建的bean, scriptSourceCache缓存了所有的脚本信息

final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory(); /** Map from bean name String to ScriptSource object */ private final Map scriptSourceCache = new HashMap(); refresh参数

创建的是JdkDynamicAopProxy代理对象, 在每一次调用这个代理对象的方法的时候, 都回去校验被代理对象是否需要刷新, 通过比对脚本文件的最后更新时间和设定的更新时间间隔, 如果需要刷新则重新加载这个groovy文件, 并编译, 然后创建一个新的bean并注册进行替换

3.内联方式配置

inline script标签, 从配置中读取源代码

综上, 扩展一下, 脱离xml配置, 可以从数据库中定时加载groovy代码, 构建/更新/删除BeanDefinition

https://docs.spring.io/spring/docs/4.3.8.RELEASE/spring-framework-reference/html/dynamic-language.html#dynamic-language-beans-ctor-injection

Groovy运行沙盒

沙盒原理也叫沙箱,英文sandbox。在计算机领域指一种虚拟技术,且多用于计算机安全技术。安全软件可以先让它在沙盒中运行,如果含有恶意行为,则禁止程序的进一步运行,而这不会对系统造成任何危害。

举个例子:

docker容器可以理解为在沙盒中运行的进程。这个沙盒包含了该进程运行所必须的资源。不同的容器之间相互隔离。CGroup实现资源控制, Namespace实现访问隔离, rootfs实现文件系统隔离。

对于嵌入Groovy的Java系统, 如果暴露接口, 可能存在的隐患有

通过Java的Runtime.getRuntime().exec()方法执行shell, 操作服务器.....

执行System.exit(0)

dump 内存中的Class, 修改内存中的缓存数据

ElasticSearch Groovy 脚本 远程代码执行漏洞

Groovy提供了编译自定义器(Compilation customizers), 无论你使用 groovyc 还是采用 GroovyShell 来编译类,要想执行脚本,实际上都会使用到编译器配置(compiler configuration)信息。这种配置信息保存了源编码或类路径这样的信息,而且还用于执行更多的操作,比如默认添加导入,显式使用 AST(语法树) 转换,或者禁止全局 AST 转换, 编译自定义器的目标在于使这些常见任务易于实现。CompilerConfiguration 类就是切入点。

groovy sandbox的实现 -> https://github.com/jenkinsci/groovy-sandbox

实现过程:

groovy-sandbox实现了一个SandboxTransformer, 扩展自CompilationCustomizer, 在Groovy代码编译时进行转换. 脚本转换后, 让脚本执行的每一步都会被拦截, 调用Checker进行检查

可拦截所有内容,包括

方法调用(实例方法和静态方法) 对象分配(即除了“this(...)”和“super(...)”之外的构造函数调用 属性访问(例如,z = foo.bar,z = foo。“bar”)和赋值(例如,foo.bar = z,foo。“bar”= z) 数组访问和赋值

当然, 执行性能也会受到一些的影响

示例: Jenkins Pipline支持在Groovy沙盒中执行Groovy脚本

image.png

其他:

Groovy元编程 原文 译文

Groovy的ClassLoader体系



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3